package AWDB::Reader;

use strict;
use warnings;
use namespace::autoclean;
use autodie;
use Carp;

use utf8;
binmode(STDOUT, ':encoding(utf8)');

use JSON;
use Encode;
use Net::IP;
use Socket 1.87 qw(inet_pton inet_ntop AF_INET AF_INET6);
use List::MoreUtils qw(zip);
use Math::BigInt qw();
use feature qw(say);
use Data::Dumper;


my %decode_type = (
    1  => 'decode_arrary',
    2  => 'decode_pointer',
    3  => 'decode_str',
    4  => 'decode_long_str',
    5  => 'decode_uint',
    6  => 'decode_int',
    7  => 'decode_float',
    8  => 'decode_double',
);

my %decode_func_type = (
    1 => 'decode_content_structure',
    2 => 'decode_content_direct',
);

sub new {
    my $class = shift;
    my %parm=@_;
    my $file = $parm{'file'};

    die qq{Error: cannot opening file "$file": The file does not exist.}
        unless -e $file;

    die qq{Error: cannot opening file "$file": The file cannot be read.}
        unless -r _;

    open my $fh, '<:raw', $file; # 不能使用<方式直接打开，win中会出现问题
    read($fh, my $buf, 2, 0);
    my $mate_len = unpack('n', $buf);

    read($fh, $buf, $mate_len, 0);
    my $mateadta = decode_json($buf);

    my %self;
    $self{'fh'} = $fh;
    $self{'node_count'} = $mateadta->{'node_count'};
    $self{'ip_version'} = $mateadta->{'ip_version'};
    $self{'byte_len'} = $mateadta->{'byte_len'};
    $self{'decode_type'} = $mateadta->{'decode_type'};
    $self{'node_count'} = $mateadta->{'node_count'};
    $self{'columns'} = $mateadta->{'columns'};
    $self{'start_len'} = $mate_len + 2;
    $self{'base_offset'} = $self{node_count} * $self{'byte_len'} * 2;

    bless \%self, $class;

    return \%self;
}

sub get_result_address {
    my $self = shift;
    my $addr = shift;

    die 'IP address is required, you must provide an IP address to look up'
        unless defined $addr && length $addr;

    $addr = _is_ip($addr);

    die "The IP address ($addr) is not a valid IPv4 or IPv6 address"
        unless $addr;

    my $pointer = $self->_find_tree_index($addr);
#    say "Node_index is: $pointer";

#    return undef unless $pointer;
    return {} unless $pointer;

    return $self->_get_record_data($pointer);
}

sub _is_ip {
    my $ip_address = shift;

    if ($ip_address =~ /^[+-]?\d+$/) {
        if ($ip_address >= 0 && $ip_address <= 4294967295) {
            $ip_address = join ".", unpack("C4", pack("N", $ip_address));
        }
        elsif ($ip_address >= 0 && $ip_address <= 340282366920938463463374607431768211455) {
#            **************** 暂不支持整数形式IPV6 ****************
#            $ip_address = inet_ntop(AF_INET6, pack('Q>', $ip_address));
            return undef; # 暂不支持整数形式IPV6
        }
        else {
            return undef; # 不是IPv4或IPv6地址，返回undef
        }
    }

    my $ip_obj = Net::IP->new($ip_address);

    if (defined $ip_obj && $ip_obj->version() == 4) {
        return $ip_obj->ip();
    }
    elsif (defined $ip_obj && $ip_obj->version() == 6) {
        return $ip_obj->ip();
    }
    else {
        return undef;
    }
}

sub _find_tree_index {
    my $self = shift;
    my $addr = shift;

    my $is_addr_ipv6 = $addr =~ /:/;

    if ($self->{'ip_version'} eq '6' && !$is_addr_ipv6) {
        die "The IP address is IPv4, but the database is IPV6";
    }
    elsif ($self->{'ip_version'} eq '4' && $is_addr_ipv6) {
        die "The IP address is IPv6, but the database is IPV4";
    }

    my $pack_addr = inet_pton($is_addr_ipv6 ? AF_INET6 : AF_INET, $addr);

    die "The IP address ($addr) is not a valid IPv4 or IPv6 address"
        unless defined $pack_addr;

    my @address_bytes = unpack( 'C*', $pack_addr );

    # node起始位置：4_6混合版中V4的起始位置96；其他都从0开始
    my $node = $self->{'ip_version'} eq '4_6' && !$is_addr_ipv6 ? 96 : 0;

    my $bit_length = @address_bytes * 8;

    for my $bit_num (0 .. $bit_length) {
        last if $node >= $self->{'node_count'};

        my $tmp_bit = 0xFF & $address_bytes[$bit_num >> 3];
        my $bit      = 1 & ($tmp_bit >> 7 - ($bit_num % 8));

        my ($l_record, $r_record) = $self->_node_read($node);

        $node = $bit ? $r_record : $l_record;
    }

    if ($node == $self->{'node_count'}) {
        say "Invalid node_index in search tree";
        return undef;
    }

    if ($node > $self->{'node_count'}) {
        return $node;
    }
}

sub _node_read {
    my $self     = shift;
    my $nodes = shift;

    my $node = q{};
    $self->_reader(
        \$node,
        $nodes * $self->{'byte_len'} * 2,
        $self->{'byte_len'} * 2,
    );

    if ($self->{'byte_len'} == 5) {
        my $left_node = substr($node, 0, 5);
        my $right_node = substr($node, 5, 5);

        return (unpack('Q>', ("\x00" x 3) . $left_node), unpack('Q>', ("\x00" x 3) . $right_node));
    }
    else {
        return unpack(NN => $node);
    }
}

sub _reader {
    my $self          = shift;
    my $buffer        = shift;
    my $offset        = shift;
    my $default_size   = shift;
    my $seek_end = shift;

    my $source = $self->{'fh'}; # awdb文件句柄
    seek $source, $offset + $self->{'start_len'}, $seek_end ? 1 : 0;

    my $read_offset = 0;
    while (1) {
        my $read_size = read(
            $source,
            ${$buffer},
            $default_size,
            $read_offset,
        );

        return if $default_size == $read_size;

        $default_size -= $read_size;
        $read_offset += $read_size;
    }

    return;
}

sub _get_record_data {
    my $self   = shift;
    my $offset = shift;

    # start_len在_reader初始化中已偏移
    my $resolved = $self->{'base_offset'} + $offset - $self->{'node_count'} - 10;

    # 判断解析方式
    my $decode_func = $decode_func_type{$self->{'decode_type'}};

    if ($decode_func eq 'decode_content_structure') {
        return $self->_map_key_value(scalar $self->_decoder_structure($resolved));
    }
    elsif ($decode_func eq 'decode_content_direct') {
        return $self->_decoder_direct($resolved);
    }
    else {
        return 'Unsupported decode_func_type';
    }
}

sub _decoder_direct {
    my $self = shift;
    my $offset = shift;

    my $data_len;
    $self->_reader(\$data_len, $offset, 4);
    $offset += 4;

    my $len = unpack('N!' => ("\x00" x (4 - length($data_len))) . $data_len);

    my $buffer;
    $self->_reader(\$buffer, $offset, $len);

    my @buffer_array = split(/\t/, Encode::decode('utf-8', $buffer, Encode::FB_CROAK));

    my %result_dict = ();
    foreach my $i (0..$#{$self->{'columns'}}) {
        $result_dict{$self->{'columns'}[$i]} = $buffer_array[$i] ? $buffer_array[$i] : '';
    }
    return \%result_dict;
}

sub _map_key_value {
    my $self = shift;
    my $values = shift;

    my $keys = $self->{'columns'};
    my %keys_dic;
    my @values_list;
    for (my $i = 0; $i < scalar(@$keys); $i++) {
        my $row = $keys->[$i];
        if (ref $row eq 'ARRAY') {
            $keys_dic{$keys->[$i - 1]} = $row;
        }
    }
    my $count = scalar(@$values);
    for (my $i = 0; $i < scalar(@$values); $i++) {
        my $row = $values->[$i];
        if (ref $row eq 'ARRAY') {
            push @values_list, $row;
            $count = $i;
        }
    }
    if (scalar(keys %keys_dic) != scalar(@values_list)) {
        die "离线库错误key和value！";
    } else {
        my %forepart_dict = ();
        @forepart_dict{@$keys[0..$count-1]} = @$values[0..$count-1];
        my %end_dict = ();
        for (my $i = 0; $i < scalar(keys %keys_dic); $i++) {
            my $key = (keys %keys_dic)[$i];
            $end_dict{$key} = [map { +{ zip(@{$keys_dic{$key}}, @$_) } } @{$values_list[$i]}];
        }
        my %result_dict = (%forepart_dict, %end_dict);
        return \%result_dict;
    }
}

sub _decoder_structure {
    my $self = shift;
    my $offset = shift;

    my $data_type;
    $self->_reader(\$data_type, $offset, 1);
    $offset++;

    $data_type = unpack(C => $data_type);

    my $type = $decode_type{$data_type};

    confess "Unexpected data type number ($data_type) ,please check!" unless defined $type;

    if ($type eq 'decode_int') {
        my $num = $self->_decode_int($offset, 4);
        $offset += 4;

        return ($num, $offset);
    }

    if ($type eq 'decode_float') {
        my $num = $self->_decode_float($offset, 4);
        $offset += 4;

        return ($num, $offset);
    }

    if ($type eq 'decode_double') {
        my $num = $self->_decode_double($offset, 8);
        $offset += 8;

        return ($num, $offset);
    }

    my $data_len;
    $self->_reader(\$data_len, $offset, 1);
    $offset++;

    my $len = unpack(C => $data_len);

    if ($type eq 'decode_long_str') {
        $self->_reader(\$data_len, $offset, $len);
        $offset += $len;
        $len = unpack(n => $data_len);

        my $str = $self->_decode_str($offset, $len);
        $offset += $len;

        return ($str, $offset);
    }

    if ($type eq 'decode_arrary') {
        return $self->_decode_array($offset, $len);
    }

    if ($type eq 'decode_pointer') {
        (my $value, $_) = $self->_decode_pointer($offset, $len);
        $offset += $len;

        return wantarray ? ($value, $offset) : $value;
    }

    if ($type eq 'decode_str') {
        my $str = $self->_decode_str($offset, $len);
        $offset += $len;

        return ($str, $offset);
    }

    if ($type eq 'decode_uint') {
        my $num = $self->_decode_uint($offset, $len);
        $offset += $len;

        return ($num, $offset);
    }
}

sub _decode_array {
    my $self = shift;
    my $offset = shift;
    my $size = shift;

    my @array;
    for (1 .. $size) {
        (my $val, $offset) = $self->_decoder_structure($offset);

        push @array, $val;
    }

    return wantarray ? (\@array, $offset) : \@array;
}

sub _decode_pointer {
    my $self = shift;
    my $offset = shift;
    my $size = shift;

    my $buffer;
    $self->_reader(\$buffer, $offset, $size);

    my $pointer = unpack('N!' => ("\x00" x (4 - length($buffer))) . $buffer); # 左侧补0

    $pointer += $self->{'base_offset'};

    return $self->_decoder_structure($pointer);
}

sub _decode_str {
    # 同时适配long_str
    my $self = shift;
    my $offset = shift;
    my $size = shift;

    return q{} if $size == 0;

    my $buffer;
    $self->_reader(\$buffer, $offset, $size);

    return Encode::decode('utf-8', $buffer, Encode::FB_CROAK);
}

sub _decode_uint {
    my $self = shift;
    my $offset = shift;
    my $size = shift;

    return q{} if $size == 0;

    my $buffer;
    $self->_reader(\$buffer, $offset, $size);

    my $max_int_bytes = log(~0) / (8 * log(2));
    my $int = $size <= $max_int_bytes ? 0 : Math::BigInt->bzero;

    return $int if $size == 0;

    my @unpacked = unpack('C*', $buffer);
    for my $piece (@unpacked) {
        $int = ($int << 8) | $piece;
    }

    return $int;

#    return unpack(C => $buffer);
}

sub _decode_int {
    my $self = shift;
    my $offset = shift;
    my $size = shift;

    return q{} if $size == 0;

    my $buffer;
    $self->_reader(\$buffer, $offset, $size);

    return unpack('N!' => ("\x00" x (4 - length($buffer))) . $buffer);
}

sub _decode_float {
    my $self = shift;
    my $offset = shift;
    my $size = shift;

    return q{} if $size == 0;

    my $buffer;
    $self->_reader(\$buffer, $offset, $size);

    return unpack('f>', $buffer);
}

sub _decode_double {
    my $self = shift;
    my $offset = shift;
    my $size = shift;

    return q{} if $size == 0;

    my $buffer;
    $self->_reader(\$buffer, $offset, $size);

    return unpack('d>', $buffer);
}

1;